/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#ifdef GIMI_CLIENT_API
#undef GIMI_CLIENT_API
#endif
#define GIMI_CLIENT_API GIMI_API_VERSION
#include "gimi.h"

#include <assert.h>

namespace gimi {

GIMI::GIMI(int defBufferSize /* = GIMI_DEFAULTQUEUESIZE */,
           const std::string cDescription /* = "" */,
           const std::string cLocation /* = "" */,
           const int cVersion /* = 0 */,
           const int cBuild /* = 0 */)
  : gimiCore (defBufferSize, cDescription, cLocation, cVersion, cBuild)
{
  dPrint(ODVINFO,"GIMI instance %p constructed", this);
}

GIMI::~GIMI()
{
  dPrint(ODVINFO,"GIMI instance %p destroyed", this);
}

bool GIMI::readSettings(const std::string &filename)
{
  gim::CSettings settings;
  
  if (settings.ReadSettingsXMLFile(filename) != true) {
    dPrint(1, "Failed to read settings file '%s'.", filename.c_str());
    return false;
  }
  
  return readSettings(settings);
}

int GIMI::getQueueSize(const int majorTypeId /* = GIMI_IGNOREMAJORTYPE */,
                       const int minorTypeId /* = GIMI_IGNOREMINORTYPE */,
                       const unsigned int bufferId /* = 0 */)
{
  int queuesize = 0;

  if (minorTypeId == GIMI_IGNOREMINORTYPE) {
    if (majorTypeId == GIMI_IGNOREMAJORTYPE) { // All queues
      queuesize = gimiCore.messageBuffers.getTotalSize(bufferId);
    } else {
      dPrint(2, "Invalid minortype %d for majortype %d", minorTypeId, majorTypeId);
      return 0;
    }
  } else { // Specific queue
    queuesize = gimiCore.messageBuffers.getQueueCurSize(majorTypeId, minorTypeId, bufferId);
  }

  return queuesize;
}

bool GIMI::addRedirection(const unsigned int bufferIdentifier,
                          const int majorTypeId,
                          const int minorTypeId /* = -1 */,
                          const std::string &senderName /* = "" */)
{
  bool result = false;
  if (senderName.empty()) {
    result = gimiCore.messageBuffers.addRedirection(bufferIdentifier,
        majorTypeId, minorTypeId);
  } else {
    dPrint(25, "Finding clientId for '%s'", senderName.c_str());
    int senderId = findClientId(senderName);
    if (senderId > 0) {
      result = gimiCore.messageBuffers.addRedirection(bufferIdentifier,
          majorTypeId, minorTypeId, senderName, senderId);
    } else {
      dPrint(1, "Error, could not find hubid for hubname %s.",
             senderName.c_str());
      result = false;
    }
  }
  return result;
}
    
bool GIMI::removeRedirection(const unsigned int bufferIdentifier,
                             const int majorTypeId,
                             const int minorTypeId /* = -1 */,
                             const std::string &senderName /* = "" */)
{
  bool result = false;
  if (senderName.empty()) {
    result = gimiCore.messageBuffers.removeRedirection(bufferIdentifier,
        majorTypeId, minorTypeId);
  } else {
    dPrint(25, "Finding clientId for '%s'", senderName.c_str());
    int senderId = findClientId(senderName);
    if (senderId > 0) {
      result = gimiCore.messageBuffers.removeRedirection(bufferIdentifier,
          majorTypeId, minorTypeId, senderId);
    } else {
      dPrint(1, "Error, could not find hubid for hubname %s.",
             senderName.c_str());
      result = false;
    }
  }
  return result;
}

int GIMI::listRedirections(t_redirectionList &redirections,
                           const bool exactTypes /* = false */,
                           const int majorTypeId /* = -1 */,
                           const int minorTypeId /* = -1 */,
                           const std::string &senderName /* = "" */)
{
  int result = 0;
  if (senderName.empty()) {
    result = gimiCore.messageBuffers.listRedirections(redirections,
        exactTypes, majorTypeId, minorTypeId);
  } else {
    dPrint(25, "Finding clientId for '%s'", senderName.c_str());
    int senderId = findClientId(senderName);
    if (senderId > 0) {
      result = gimiCore.messageBuffers.listRedirections(redirections,
          exactTypes, majorTypeId, minorTypeId, senderId);
    } else {
      dPrint(1, "Error, could not find id for name %s.",
             senderName.c_str());
      result = -1;
    }
  }
  return result;
}

int GIMI::send(const std::string &targetName,
               const char *data,
               const int datalen,
               const int majorTypeId,
               const int minorTypeId,
               const int messageId /* = 0 */,
               const int messageFlags /* = 0*/)
{
  int result = GIMI_ERROR;  
  int targetId = findClientId(targetName);

  if (targetId > 0) {
    result = send(targetId, data, datalen, majorTypeId, minorTypeId, messageId, messageFlags);
  } else if (targetId == 0) {
    dPrint(2,"Target '%s' could not be located!", targetName.c_str());
    result = GIMI_ERROR;
  } else {
    dPrint(2, "Not connected to tcpHub.");
    result = GIMI_NOCONNECTION;
  }

  return result;
}

int GIMI::receive(GIMIMessage &message,
                  const int timeout,
                  const int majorTypeId /* = GIMIBUFFER_IGNOREMAJORTYPE */,
                  const int minorTypeId /* = GIMIBUFFER_IGNOREMINORTYPE */,
                  const unsigned int bufferId /* = 0 */)
{
  int result = GIMI_ERROR;
  GIMIMessage *msg = NULL;
  
  // Mutexes and timeouting handled in MultiBuffer
  result = gimiCore.messageBuffers.popMessage(&msg, majorTypeId, minorTypeId, timeout,
                                              bufferId);
  
  if (msg != NULL && result == GIMI_OK) {
    // This is a TRANSFER! (On purpose!)
    message.assign(*msg);
    delete msg;
  }
  //dPrint(19, "Receive resulted in %d", result);
  return result;
}

int GIMI::receiveRange(GIMIMessage &message,
                       const int timeout,
                       const int majorTypeIdMin,
                       const int majorTypeIdMax,
                       const int minorTypeIdMin /* = GIMI_IGNOREMINORTYPE */,
                       const int minorTypeIdMax /* = GIMI_IGNOREMINORTYPE */,
                       const unsigned int bufferId /* = 0 */)
{
  int result = GIMI_ERROR;
  GIMIMessage *msg = NULL;
  
  // Try to pop a message. If not found, wait if timeout is defined.
  result = gimiCore.messageBuffers.popMessageRange(&msg, majorTypeIdMin, majorTypeIdMax,
                                                   minorTypeIdMin, minorTypeIdMax,
                                                   timeout, bufferId);
  
  if (msg != NULL && result == GIMI_OK) {
    // This is a TRANSFER! (On purpose!)
    message.assign(*msg);
    delete msg;
  }
  dPrint(19, "Receive resulted in %d", result);
  return result;
}

int GIMI::getCongestionLevel(const uint32_t &aNodeId)
{
  return gimiCore.iConnector->GetCongestionLevel(aNodeId);
}

int GIMI::getCongestionLevel(const std::string &aNodeName)
{
  int result = -1;  
  int targetId = findClientId(aNodeName);

  if (targetId > 0) {
    return getCongestionLevel((uint32_t)targetId);

  } else if (targetId == 0) {
    dPrint(2,"Target '%s' could not be located!", aNodeName.c_str());
    result = GIMI_ERROR;

  } else {
    dPrint(2, "Not connected to tcpHub.");
    result = GIMI_NOCONNECTION;

  }

  return result; 
}

bool GIMI::getError(const uint32_t &aNodeId)
{
  return gimiCore.iConnector->GetError(aNodeId);  
}

bool GIMI::getError(const std::string &aNodeName)
{ 
  bool result = true;
  int targetId = findClientId(aNodeName);

  if (targetId > 0) {
    result = getError((uint32_t)targetId);

  } else if (targetId == 0) {
    dPrint(2,"Target '%s' could not be located!", aNodeName.c_str());

  } else {
    dPrint(2, "Not connected to tcpHub.");

  }

  return result; 
}


int GIMI::serviceDiscoveryToTarget(t_serviceList &servicelist,
                                   const std::string &clientname,
                                   const int timeoutms)
{
  int clientId = findClientId(clientname);

  if (clientId == 0) {
    dPrint(2,"Target '%s' could not be located!", clientname.c_str());
    return GIMI_ERROR;

  } else if (clientId < 0) {
    dPrint(2, "Not connected to nif.");
    return GIMI_NOCONNECTION;
  
  }

  return serviceDiscoveryToTarget(servicelist, clientId, timeoutms);
}

int GIMI::serviceDiscoveryToTarget(t_serviceList &servicelist,
                                   const int clientId,
                                   const int timeoutms)
{
  servicelist.clear();
  return gimiCore.serviceDiscoveryToTarget(servicelist, clientId, timeoutms);
}

int GIMI::subscribeToService(const std::string &clientName,
                             const int majorServiceId,
                             const int minorServiceId,
                             const int timeoutms /* = 5000 */,
                             const int updateInterval /* = -1 */)
{ 
  int clientId = findClientId(clientName);

  if (clientId == 0) {
    dPrint(2,"Target '%s' could not be located!", clientName.c_str());
    return GIMI_ERROR;
  } else if (clientId < 0) {
    dPrint(2, "Not connected to nif.");
    return GIMI_NOCONNECTION;
  }

  return subscribeToService(clientId, 
                            majorServiceId, minorServiceId, 
                            timeoutms, updateInterval);
}

int GIMI::subscribeToService(const int clientId,
                             const int majorServiceId,
                             const int minorServiceId,
                             const int timeoutms /* = 5000 */,
                             const int updateInterval /* = -1 */)
{
  int result = gimiCore.subscribeCore(clientId, majorServiceId, minorServiceId, false, timeoutms);

  // Add automatic resubscription if update-interval is specified
  if (updateInterval > 0) {
    gimiCore.subscriptions.addSubscription(clientId, majorServiceId, minorServiceId,
                                           updateInterval);
    dPrint(20, "Added automatic resubscription %d:%d to client 0x%08x with updateinterval %d.",
            majorServiceId, minorServiceId, clientId, updateInterval);
  }
  
  return result;
}

int GIMI::unsubscribeService(const std::string &clientName,
                             const int majorServiceId,
                             const int minorServiceId,
                             const int timeoutms)
{
  int clientId = findClientId(clientName);

  if (clientId == 0) {
    dPrint(2,"Target '%s' could not be located!", clientName.c_str());
    return GIMI_ERROR;
  } else if (clientId < 0) {
    dPrint(2, "Not connected to nif.");
    return GIMI_NOCONNECTION;
  }
  
  return unsubscribeService(clientId, majorServiceId, minorServiceId, timeoutms);
}

int GIMI::unsubscribeService(const int clientId,
                             const int majorServiceId,
                             const int minorServiceId,
                             const int timeoutms)
{
  // Remove auto-subscribe if exists
  gimiCore.subscriptions.removeSubscription(clientId, majorServiceId, minorServiceId);
  
  // Send unsubscribe
  int result = gimiCore.subscribeCore(clientId, majorServiceId, minorServiceId,
                                      true, timeoutms);
  return result;
}

int GIMI::ping(const std::string &clientname,
               const int timeoutms)
{
  // Don't do anything if not connected.
  if (!gimiCore.isConnected()) {
    dPrint(1, "We are not connected to hub!");
    return GIMI_NOCONNECTION;
  }

  int clientId = findClientId(clientname);

  if (clientId == 0) {
    dPrint(2,"Target '%s' could not be located!", clientname.c_str());
    return GIMI_ERROR;
  } else if (clientId < 0) {
    dPrint(2, "Not connected to nif.");
    return GIMI_NOCONNECTION;
  }
  
  return ping(clientId, timeoutms);
}

int GIMI::setTargetDebugLevel(const std::string &clientname,
                              const int debugLevel)
{
  dPrint(10, "Called");
  // Don't do anything if not connected.
  if (!gimiCore.isConnected()) {
    dPrint(1, "We are not connected to hub!");
    return GIMI_NOCONNECTION;
  }

  int clientId = findClientId(clientname);

  if (clientId == 0) {
    dPrint(2,"Target '%s' could not be located!", clientname.c_str());
    return GIMI_ERROR;
  } else if (clientId < 0) {
    dPrint(2, "Not connected to nif.");
    return GIMI_NOCONNECTION;
  }
  
  return setTargetDebugLevel(clientId, debugLevel);
}

bool GIMI::findClientName(std::string &clientname,
                          const int idno,
                          const unsigned int aTimeout_ms)
{
  int result = gimiCore.iConnector->findClientName(clientname, idno, aTimeout_ms);
  
  if (result != GIMI_OK) {
    return false;
  } 
  
  return true;
}

bool GIMI::findClientId(uint32_t &aId,
                        const std::string &clientname,
                        const unsigned int aTimeout_ms)
{
  const int result = gimiCore.iConnector->findClientId(aId, clientname, aTimeout_ms);
  if (result == GIMI_OK) {
    return true;
  } else {
    return false;
  }
}


int GIMI::findClientId(const std::string &clientname,
                       const unsigned int aTimeout_ms)
{
  uint32_t clientId = 0;
  int result = gimiCore.iConnector->findClientId(clientId, clientname, aTimeout_ms);
  if (result == GIMI_OK) {
    return clientId;
  } else if (result == GIMI_INVALIDCLIENT) {
    return 0;
  } else {
    return result;
  }
}

int GIMI::getSubscriberCount(const int majorTypeId,
                             const int minorTypeId
                                 /* = GIMIBUFFER_IGNOREMINORTYPE */) const
{
  //ownMutex_Lock(subscriberMutex);
  int result = gimiCore.subscribers.getSubscriberCount(majorTypeId, minorTypeId);
  //ownMutex_Unlock(subscriberMutex);
  return result;
}

} // end namespace gimi
